package edu.amm.neur;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;

/**
 *
 * @author Илья
 */
public class GraphicsUtils {
	
	private static final int SAMPLE_SIZE = 8;
	private static final double COLOR_PERCENT = 5;
	
	private static final double COLORED_FRACTION = COLOR_PERCENT / 100;
	private static final int SAMPLE_SQUARE = SAMPLE_SIZE * SAMPLE_SIZE;
	
	/**
	 * Очищает канву и заполняет её заданным цветом.
	 * @param canvas Канва.
	 * @param color Цвет.
	 */
	public static void clear(Canvas canvas, Color color) {
		if (canvas == null || color == null)
			return;
		
		GraphicsContext context = canvas.getGraphicsContext2D();
		context.save();
		
		context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
		context.setFill(color);
		context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
		
		context.restore();
	}
	
	/**
	 * Масштабирует монохромное изображение в полный размер растра.
	 * Масштабированное изображение записывается в переданное изображение.
	 * @param image Изображение.
	 * @param color Цвет изображения.
	 * @return <code>true</code>, если произошло масштабирование.
	 */
	public static boolean maximize(WritableImage image, Color color) {
		if (image == null || color == null)
			return false;
		
		int width = (int) image.getWidth();
		int height = (int) image.getHeight();
		int minX = width - 1;
		int minY = height - 1;
		int maxX = 0;
		int maxY = 0;
		PixelReader reader = image.getPixelReader();
		for (int j = 0; j < height; j++)
			for (int i = 0; i < width; i++) {
				if (!reader.getColor(i, j).equals(color))
					continue;
				
				minX = Math.min(minX, i);
				minY = Math.min(minY, j);
				maxX = Math.max(maxX, i);
				maxY = Math.max(maxY, j);
			}
		
		int actualWidth = maxX - minX + 1;
		int actualHeight = maxY - minY + 1;
		if (actualWidth <= 0 || actualHeight <= 0)
			return false;
		
		BufferedImage buff = new BufferedImage(actualWidth, actualHeight, BufferedImage.TYPE_INT_ARGB);
		for (int j = 0, J = minY; J <= maxY; j++, J++)
			for (int i = 0, I = minX; I <= maxX; i++, I++)
				buff.setRGB(i, j, reader.getArgb(I, J));
		
		double scaleX = (double) width / actualWidth;
		double scaleY = (double) height / actualHeight;
		double scale = Math.min(scaleX, scaleY);
		
		BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D graphics = scaledImage.createGraphics();
		graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
		AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
		graphics.drawImage(buff, transform, null);
		graphics.dispose();
		
		PixelWriter writer = image.getPixelWriter();
		for (int j = 0; j < height; j++)
			for (int i = 0; i < width; i++)
				writer.setArgb(i, j, scaledImage.getRGB(i, j));
		
		return true;
	}
	
	/**
	 * Отрисовывает на канве изображение, предварительно очищая прежнее содержимое канвы.
	 * 
	 * @param canvas Канва.
	 * @param image Изображение.
	 * @param backColor Цвет фона изображения.
	 */
	public static void draw(Canvas canvas, WritableImage image, Color backColor) {
		if (canvas == null || backColor == null || image == null)
			return;
		
		clear(canvas, backColor);
		canvas.getGraphicsContext2D().drawImage(image, 0, 0);
	}
	
	/**
	 * Оцифровка монохромного изображения.
	 * Разбивает растр на квадраты <code>{@link #SAMPLE_SIZE} x {@link #SAMPLE_SIZE}</code>.
	 * Квадрату ставится в соответствие 1, если он на <code>{@link #COLOR_PERCENT}</code>
	 * или более процентов закрашен цветом <code>color</code>, иначе ставится в соответствие 0.
	 * Если в растр не укладывается целое число квадратов, то "нецелые" квадраты
	 * дополняются до "целых" цветом, соответствующим фону изображения
	 * (фон - любой цвет, не совпадающий с цветом изображения).
	 * Элементы результирующего вектора соответствуют квадратам,
	 * полученным при обходе растра слева направо, сверху вниз.
	 * 
	 * @param image Изображение.
	 * @param color Цвет изображения.
	 * @return Вектор - результат оцифровки.
	 */
	public static boolean[] digitize(WritableImage image, Color color) {
		if (image == null || color == null)
			return null;
		
		int width = (int) image.getWidth();
		int height = (int) image.getHeight();
		int length = getDigitizedVectorLength(width, height);
		
		int i = 0;
		boolean[] digitized = new boolean[length];
		for (int minY = 0; minY < height; minY += SAMPLE_SIZE)
			for (int minX = 0; minX < width; minX += SAMPLE_SIZE) {
				int maxY = Math.min(minY + SAMPLE_SIZE, height) - 1;
				int maxX = Math.min(minX + SAMPLE_SIZE, width) - 1;
				
				digitized[i++] = digitize(image, minX, minY, maxX, maxY, color);
			}
		
		return digitized;
	}
	
	/**
	 * @param width Ширина изображения.
	 * @param height Высота изображения.
	 * @return Длина вектора-оцифровки изображения.
	 */
	public static int getDigitizedVectorLength(int width, int height) {
		if (width < 1 || height < 1)
			return -1;
		
		return getSamplesCount(width) * getSamplesCount(height);
	}
	
	private static boolean digitize(WritableImage image, int minX, int minY, int maxX, int maxY, Color color) {
		int colored = 0;
		PixelReader reader = image.getPixelReader();
		for (int j = minY; j <= maxY; j++)
			for (int i = minX; i <= maxX; i++)
				if (color.equals(reader.getColor(i, j)))
					colored++;
		
		return (double) colored / SAMPLE_SQUARE < COLORED_FRACTION ? false : true;
	}
	
	private static int getSamplesCount(int length) {
		double x = (double) length / SAMPLE_SIZE;
		double floor = Math.floor(x);
		
		return (int) floor + ((x - floor) > 0 ? 1 : 0);
	}
}